﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Represents an instantaneous dynamic mark, i.e. a dynamic mark with no graduation.
    /// Examples of instantaneous dynamic mark are forte, sforzando, piano etc.
    /// </summary>
    public class DynamicMarkElement : AttachmentElement
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMarkElement"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat that directly owns this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        public DynamicMarkElement(CompositionPart packagePart, BeatElement owner)
            : base(packagePart, owner)
        {
            Degree = 1;
            Expression = "";
            MarkType = DynamicMarkType.MezzoForte;
            Subito = false;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMarkElement"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the serialized data of this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal DynamicMarkElement(CompositionPart packagePart, BeatElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            degreeAttribute = element.Attributes[PackageVocabulary.DegreeAttribute];
            expressionAttribute = element.Attributes[PackageVocabulary.ExpressionAttribute];
            markTypeAttribute = element.Attributes[PackageVocabulary.MarkTypeAttribute];
            subitoAttribute = element.Attributes[PackageVocabulary.SubitoAttribute];
        }

        #endregion

        #region AttachmentElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElementCore(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.DynamicMarkElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            degreeAttribute = document.CreateAttribute(PackageVocabulary.DegreeAttribute);
            expressionAttribute = document.CreateAttribute(PackageVocabulary.ExpressionAttribute);
            markTypeAttribute = document.CreateAttribute(PackageVocabulary.MarkTypeAttribute);
            subitoAttribute = document.CreateAttribute(PackageVocabulary.SubitoAttribute);

            element.Attributes.Append(degreeAttribute);
            element.Attributes.Append(expressionAttribute);
            element.Attributes.Append(markTypeAttribute);
            element.Attributes.Append(subitoAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the degree of the dynamic mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The degree is used for <see cref="DynamicMarkType.Piano"/>
        /// and <see cref="DynamicMarkType.Forte"/> marks to indicate
        /// an even softer or even louder dynamic.<br/>
        /// For example <see cref="DynamicMarkType.Piano"/> and <see cref="Degree"/> 2
        /// means <i>pp</i> = pianissimo, <see cref="Degree"/> 3 stands for <i>ppp</i> = piano-pianissimo etc.<br/>
        /// The degree can never be 0 and must be 1 for any dynamic mark type other than <see cref="DynamicMarkType.Piano"/>
        /// or <see cref="DynamicMarkType.Forte"/>.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is 0.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> is greater than 1 but the <see cref="MarkType"/> is different from <see cref="DynamicMarkType.Piano"/>
        /// or <see cref="DynamicMarkType.Forte"/>.
        /// </exception>
        /// TODO: Write the validation rules
        public byte Degree
        {
            get { return byte.Parse(degreeAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("Degree", value);

                degreeAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("Degree");
            }
        }

        /// <summary>
        /// Gets or sets the additional text expression.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The expression is usually written in front of the mark symbol and accentuates
        /// or further explains its meaning. Examples are <i>meno</i> (less), <i>più</i> (more) or <i>poco</i> (somewhat).
        /// </para>
        /// </value>
        public string Expression
        {
            get { return expressionAttribute.Value; }
            set
            {
                expressionAttribute.Value = value;

                RaisePropertyChanged("Expression");
            }
        }

        /// <summary>
        /// Gets or sets the type of the dynamic mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DynamicMarkType"/>
        /// </para>
        /// <para>
        /// The type of the dynamic mark. Default value is <see cref="DynamicMarkType.MezzoPiano"/>.<br/>
        /// When the type is set to a value other than <see cref="DynamicMarkType.Piano"/>
        /// or <see cref="DynamicMarkType.Forte"/>, the <see cref="Degree"/> is automatically set to 1.
        /// </para>
        /// </value>
        public DynamicMarkType MarkType
        {
            get { return (DynamicMarkType)Enum.Parse(typeof(DynamicMarkType), markTypeAttribute.Value); }
            set
            {
                if (value != DynamicMarkType.Piano && value != DynamicMarkType.Forte)
                    Degree = 1;
                markTypeAttribute.Value = value.ToString();

                RaisePropertyChanged("MarkType");
            }
        }

        /// <summary>
        /// Gets or sets the value indicating whether this mark indicates a subito (sudden) change.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the mark indicates a subito (sudden) change. Default value is <see langword="false"/>.<br/>
        /// In common western music notation the subito is represented by a small letter "s" infront of the mark,
        /// e.g. <i>sp</i> for subito piano or <i>sfz</i> for subito forzando (or sforzando).
        /// </para>
        /// </value>
        public bool Subito
        {
            get { return bool.Parse(subitoAttribute.Value); }
            set
            {
                subitoAttribute.Value = value.ToString();

                RaisePropertyChanged("Subito");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Degree"/> property.
        /// </summary>
        private XmlAttribute degreeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Expression"/> property.
        /// </summary>
        private XmlAttribute expressionAttribute;
        /// <summary>
        /// Holds the value of the <see cref="MarkType"/> property.
        /// </summary>
        private XmlAttribute markTypeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Subito"/> property.
        /// </summary>
        private XmlAttribute subitoAttribute;

        #endregion
    }

    /// <summary>
    /// Determines the type of a <see cref="DynamicMarkElement"/>.
    /// </summary>
    public enum DynamicMarkType
    {
        /// <summary>
        /// Piano (soft).
        /// </summary>
        Piano,
        /// <summary>
        /// Mezzo-piano (moderately soft).
        /// </summary>
        MezzoPiano,
        /// <summary>
        /// Mezzo-forte (moderately loud).
        /// </summary>
        MezzoForte,
        /// <summary>
        /// Forte (loud).
        /// </summary>
        Forte,
        /// <summary>
        /// Forzando (forcefull note).
        /// </summary>
        Forzando,
        /// <summary>
        /// Piano-Forte (first soft than loud).
        /// </summary>
        PianoForte,
        /// <summary>
        /// Forte-Piano (first loud than soft).
        /// </summary>
        FortePiano,
        /// <summary>
        /// Rinforzando (reinforced).
        /// </summary>
        Rinforzando,
        /// <summary>
        /// Rinforte.
        /// </summary>
        Rinforte
    }
}
